﻿using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

/// <summary>
///     <see cref="Lexer" />只负责将扫描的文本转换为<see cref="SourceNode" />，
///     不核对文本上下文类型，关键字和符号的上下文类型将在<see cref="Parser" />中
///     进行核对。
/// </summary>
public class Lexer
{
    private const long MaxInteger = 2147483647;
    private const long MinInteger = -2147483648;


    private static readonly char[] NumberChars =
        ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];

    private readonly SyntaxList<SyntaxComment> _comments = [];

    private DiagnosticStorage? _diagnosticStorage;
    private SourceTextFile _source = null!;
    private LexerState _state;

    private List<DiagnosticInfo>? _unhandledDiagnostics;


    /// <summary>
    ///     当前Token前的注释列表。
    /// </summary>
    public Span<SyntaxComment> GetComments()
    {
        return _state.GetComments();
    }


    /// <summary>
    ///     获取当前节点区间的注释列表。
    /// </summary>
    public Span<SyntaxComment> GetCurrentComments()
    {
        return _state.GetCurrentComments();
    }


    /// <summary>
    ///     重新设置语法扫描器的文本内容和相关信息。
    /// </summary>
    /// <param name="source">待扫描的源文件对象</param>
    /// <param name="diagnosticStorage">错误存储对象</param>
    public void Initialize(SourceTextFile source, DiagnosticStorage? diagnosticStorage = null)
    {
        _comments.Clear();
        _state = new LexerState(source.Uri, source.GetText(), 0, _comments);
        _source = source;
        _diagnosticStorage = diagnosticStorage;
        _unhandledDiagnostics = null;
    }


    /// <summary>
    ///     获取指定区间的文本，区间为左闭右开区间，如果区间无效，返回空字符串。
    /// </summary>
    public string GetText(int start, int length)
    {
        return _state.Substring(start, length);
    }


    private void Raise(DiagnosticDescriptor descriptor, int start, int length, params object?[] msgArgs)
    {
        DiagnosticInfo info = new(
            SyntaxLocation.Create(_source.Uri, start, length), descriptor, msgArgs);

        if (_diagnosticStorage != null)
        {
            _diagnosticStorage.Raise(info);
        }
        else
        {
            _unhandledDiagnostics ??= [];
            _unhandledDiagnostics.Add(info);
        }
    }


    /// <summary>
    ///     获取扫描器产生的错误信息列表。
    /// </summary>
    public IEnumerable<DiagnosticInfo> GetDiagnostics()
    {
        if (_diagnosticStorage != null)
        {
            return _diagnosticStorage;
        }

        return _unhandledDiagnostics != null ? [.._unhandledDiagnostics] : [];
    }


    private SourceNode FinishToken(SyntaxKinds kind, string? tokenText = null, uint step = 1,
        SyntaxNodeFlags nodeFlags = SyntaxNodeFlags.None)
    {
        SourceNode node = _state.StepToken(kind, (int)step, tokenText, nodeFlags);
        // 添加节点前注释
        if (_state.IsCurrentCommentAttached)
        {
            node.SetLeadingComment(_state.GetCurrentComment());
        }

        node.AddLeadingComments(_state.GetCurrentComments());
        // PrecedingLinebreak
        if (_state.GetFlags(SyntaxNodeFlags.PrecedingLinebreak))
        {
            node.SetFlags(SyntaxNodeFlags.PrecedingLinebreak);
        }

        // 节点后的注释
        _state.ClearFlags();
        _state.SkipTrivia();
        // trailing attached
        SyntaxComment? trailing = _state.GetCurrentComment();
        if (trailing?.LeadingEmptyLines == 0)
        {
            node.SetTrailingComment(trailing);
        }

        node.AddTrailingComments(_state.GetCurrentComments());
        // TrialingLinebreak
        if (_state.GetFlags(SyntaxNodeFlags.PrecedingLinebreak))
        {
            node.SetFlags(SyntaxNodeFlags.TrailingLinebreak);
        }

        // 移除 StartOfFile
        if (_state.AtStartOfFile)
        {
            _state.RemoveFlags(SyntaxNodeFlags.StartOfFile);
        }

        return node;
    }


    /// <summary>
    ///     调用<paramref name="callback" />，并且如果 <paramref name="fallbackIfTrue" />
    ///     回调返回<see langword="true" />时，将扫描指针恢复到调用<paramref name="callback" />之前。
    /// </summary>
    private void LookAhead(Action callback, Func<bool> fallbackIfTrue)
    {
        LexerState saved = _state;
        callback();
        if (fallbackIfTrue())
        {
            _state = saved;
        }
    }


    /// <summary>
    ///     调用<paramref name="callback" />，如果<paramref name="callback" />返回<see langword="true" />，
    ///     不会回溯文本扫描位置，否则，回溯到调用之前的位置。
    /// </summary>
    public bool TryLex(Func<bool> callback)
    {
        bool res = false;
        LookAhead(() => res = callback(), () => !res);
        return res;
    }


    /// <summary>
    ///     尝试扫描指定<see cref="SyntaxKinds" />类型节点，如果不符合，将回退到之前的位置。
    /// </summary>
    /// <param name="kind">指定语法节点类型</param>
    /// <param name="node">得到的语法节点</param>
    /// <param name="allowLinefeed">是否允许换行</param>
    /// <returns>如果成功得到指定节点，返回<see langword="true" /></returns>
    public bool TryLex(SyntaxKinds kind, [MaybeNullWhen(false)] out SourceNode node,
        bool allowLinefeed = false)
    {
        return TryLex([kind], out node, allowLinefeed);
    }


    /// <summary>
    ///     尝试扫描指定<see cref="SyntaxKinds" />类型列表中的Token，如果不符合，将回退到之前的位置。
    /// </summary>
    /// <param name="kinds">指定语法节点类型列表</param>
    /// <param name="node">得到的语法节点</param>
    /// <param name="allowLinefeed">是否允许换行</param>
    /// <returns>如果成功得到指定节点，返回<see langword="true" /></returns>
    private bool TryLex(SyntaxKinds[] kinds, [MaybeNullWhen(false)] out SourceNode node,
        bool allowLinefeed = false)
    {
        node = null;
        SourceNode? resultNode = null;

        LookAhead(() => { resultNode = Lex(); },
            () =>
            {
                if (resultNode != null &&
                    resultNode.IsOneOfNode(kinds) &&
                    (allowLinefeed || !resultNode.HasPrecedingLinebreak))
                {
                    return false;
                }

                resultNode = null;
                return true;
            });

        if (resultNode == null)
        {
            return false;
        }

        node = resultNode;
        return true;
    }


    /// <summary>
    ///     读取下一个Token。
    /// </summary>
    public SourceNode Lex()
    {
        // 在文本开头手动跳过一次无用文本，后面会在 FinishToken 方法中跳过。
        if (_state.AtStartOfFile)
        {
            _state.SkipTrivia();
        }

        _state.SkipWrappedLine();
        _state.StartToken();

        char c = _state.GetCurrentChar();

        switch (c)
        {
            case '\0':
                return FinishToken(SyntaxKinds.Eof, "");

            case '(':
                return FinishToken(SyntaxKinds.LeftParenthesisToken, "(");

            case ')':
                return FinishToken(SyntaxKinds.RightParenthesisToken, ")");

            case '[':
                return FinishToken(SyntaxKinds.LeftBracketToken, "[");

            case ']':
                return FinishToken(SyntaxKinds.RightBracketToken, "]");

            case '{':
                return FinishToken(SyntaxKinds.LeftCurlyToken, "{");

            case '}':
                return FinishToken(SyntaxKinds.RightCurlyToken, "}");

            case '^':
                return FinishToken(SyntaxKinds.CaretToken, "^");

            case ';':
                return FinishToken(SyntaxKinds.SemicolonToken, ";");

            case ':':
                return FinishToken(SyntaxKinds.ColonToken, ":");

            case ',':
                return FinishToken(SyntaxKinds.CommaToken, ",");

            case '+':
                return FinishToken(SyntaxKinds.PlusToken, "+");

            case '-':
                return FinishToken(SyntaxKinds.MinusToken, "-");

            case '=':
                // =*
                if (TryLex('*'))
                {
                    return FinishToken(SyntaxKinds.EqualAsteriskToken, "=*");
                }

                // ==
                return TryLex('=')
                    ? FinishToken(SyntaxKinds.EqualEqualToken, "==")
                    : FinishToken(SyntaxKinds.EqualToken, "=");

            case '*':
                return FinishToken(SyntaxKinds.AsteriskToken, "*");

            case '/':
                return FinishToken(SyntaxKinds.SlashToken, "/");

            case '>':
                // >=
                return TryLex('=')
                    ? FinishToken(SyntaxKinds.GreaterEqualToken, ">=")
                    : FinishToken(SyntaxKinds.GreaterToken, ">");

            case '<':
                // <=
                if (TryLex('='))
                {
                    return FinishToken(SyntaxKinds.LessEqualToken, "<");
                }

                // <>
                return TryLex('>')
                    ? FinishToken(SyntaxKinds.NotEqualToken, "<>")
                    : FinishToken(SyntaxKinds.LessToken, "<");

            // Preprocessor
            case '#':
                // ##
                if (TryLex('#'))
                {
                    return FinishToken(SyntaxKinds.HashHashToken, "##");
                }
                // #include - #error

                SyntaxKinds ppKind = SyntaxKinds.Unknown;
                SyntaxNodeFlags flag = SyntaxNodeFlags.None;
                string kwText = string.Empty;

                return TryLex(() =>
                {
                    flag = _state.GetFlags();
                    char startCh = _state.NextNotWhitespace();

                    if (startCh != 'i' &&
                        startCh != 'd' &&
                        startCh != 'e' &&
                        startCh != 'l' &&
                        startCh != 'u')
                    {
                        return false;
                    }

                    kwText = LexIdentifierText(startCh);

                    if (!SyntaxFacts.TryConvertStringToKeyword(
                            "#" + kwText,
                            KeywordContexts.Preprocessor,
                            out ppKind))
                    {
                        return false;
                    }

                    if (ppKind == SyntaxKinds.PreIfKeywordToken)
                    {
                        _state.SetFlags(SyntaxNodeFlags.PreprocessorIfCondition);
                    }

                    return true;
                })
                    ? FinishToken(ppKind, "#" + kwText, 0, flag)
                    : FinishToken(SyntaxKinds.HashToken, "#");

            case '.':
                // ..
                if (TryLex('.'))
                {
                    // ...
                    return TryLex('.')
                        ? FinishToken(SyntaxKinds.DotDotDotToken, "...")
                        : FinishToken(SyntaxKinds.DotDotToken, "..");
                }

                return FinishToken(SyntaxKinds.DotToken, ".");

            case '"':
                return LexString();

            case '&':
                SourceNode? node = null;
                TryLex(
                    (['o', 'O'], () => node = LexOctalNumber()),
                    (['h', 'H'], () => node = LexHexNumber()),
                    (['&'], () => node = FinishToken(SyntaxKinds.AmpersandAmpersandToken, "&&")));

                if (node != null)
                {
                    return node;
                }

                Raise(DiagnosticDescriptors.SyntaxError,
                    _state.GetPosition(), _state.GetPosition() + 1, "&");
                break;

            case '|':
                // ||
                if (TryLex('|'))
                {
                    return FinishToken(SyntaxKinds.BarBarToken, "||");
                }

                break;

            case '\\':
                // \\.
                if (TryLexContinuousChars(['\\', '.']))
                {
                    return FinishToken(SyntaxKinds.BackSlashBackSlashDotToken, "\\\\.");
                }

                break;

            case '!':
                // !=
                if (TryLex('='))
                {
                    return FinishToken(SyntaxKinds.ExclamationEqualToken, "!=");
                }

                return FinishToken(SyntaxKinds.ExclamationToken, "!");

            case >= '0' and <= '9':
                return LexDecimalNumber();

            default:
                // identifier
                if (StringHelper.IsValidIdentifierStartChar(c))
                {
                    return LexIdentifier(c);
                }

                break;
        }

        return FinishToken(SyntaxKinds.Unknown, c.ToString());
    }


    /// <summary>
    ///     如果当前的文本扫描指针到了文本末尾，返回<see langword="true" />
    /// </summary>
    /// <returns></returns>
    public bool Eof()
    {
        return _state.Eof();
    }


    /// <summary>
    ///     先前读取内容直到换行符，并返回读取到的文本内容。
    /// </summary>
    /// <returns>调用此方法时的文本指针位置到该行末的文本内容</returns>
    public SourceNode ReadToLinefeed()
    {
        _state.StartToken();
        string text = _state.ReadToLineFeed();
        return FinishToken(SyntaxKinds.String, text);
    }


    private bool TryLex(Func<char, bool> charTester)
    {
        // 此方法不需要跳过注释，_state只需要浅拷贝。
        LexerState backup = _state;
        char ahead = _state.LookAhead();
        // 可能是换行符，交给 _state.Next 方法处理
        if (ahead is '\\' or '_')
        {
            ahead = _state.Next();
        }

        // 字符在给定列表中时，直接步进并返回true
        if (charTester(ahead))
        {
            _state.Next();
            return true;
        }

        _state = backup;
        return false;
    }


    private bool TryLex(params char[] chs)
    {
        return TryLex(ch => Array.IndexOf(chs, ch) != -1);
    }


    private void TryLex(params (char[] Chars, Action Callback)[] switches)
    {
        Action action = () => { };
        TryLex(ch =>
        {
            foreach ((char[] chars, Action callback) in switches)
            {
                if (Array.IndexOf(chars, ch) == -1)
                {
                    continue;
                }

                action = callback;
                return true;
            }

            return false;
        });

        action();
    }


    private bool TryLexContinuousChars(char[] chs)
    {
        Debug.Assert(chs.Length > 0);
        LexerState backup = _state;
        bool res = chs.All(ch => _state.Next() == ch);

        if (!res)
        {
            _state = backup;
        }

        return res;
    }


    /**
     * 字符串分为两种，
     * 1. 单行字符串，以双引号开头和结尾     - "SingleLineString"
     * 中间可以使用换行符换行  - "Single\
     * Line_
     * String"
     * 
     * 2. 多行字符串，使用 "! 开头，!" 结尾，要求"!后换行，!"前是换行符。
     */
    private SourceNode LexString()
    {
        // "!
        if (_state.LookAheadIs('!'))
        {
            _state.SetFlags(SyntaxNodeFlags.MultilineString);
            _state.Step(1);
        }

        StringBuilder str = new();

        while (!Eof())
        {
            char c = _state.Next();

            if (_state.GetFlags(SyntaxNodeFlags.MultilineString) &&
                c == '!' && _state.LookAhead() == '"')
            {
                _state.Step(2);
                break;
            }

            if (c == '"')
            {
                // 连续两个双引号("")是双引号的转义符号
                if (_state.LookAhead() == '"')
                {
                    // 跳过当前的双引号
                    _state.Step(1);
                }
                else
                {
                    break;
                }
            }

            str.Append(c);
        }

        if (Eof())
        {
            _state.SetFlags(SyntaxNodeFlags.UnterminatedString);
            Raise(DiagnosticDescriptors.UnterminatedStringLiteral,
                _state.GetTokenStart(),
                _state.GetTokenEnd());
        }

        return FinishToken(SyntaxKinds.String, str.ToString());
    }


    private string LexIdentifierText(char startChar)
    {
        StringBuilder str = new();
        str.Append(startChar);

        while (!Eof())
        {
            char c = _state.Next();

            if (StringHelper.IsValidIdentifierPartChar(c))
            {
                str.Append(c);
            }
            else
            {
                break;
            }
        }

        return str.ToString();
    }


    private SourceNode LexIdentifier(char startChar)
    {
        string str = LexIdentifierText(startChar);

        return FinishToken(SyntaxFacts.TryConvertStringToKeyword(str,
            KeywordContexts.All, out SyntaxKinds kind)
            ? kind
            : SyntaxKinds.Identifier, str, 0);
    }


    private int ParseIntegerNumberValue(StringBuilder text, NumberKinds kind)
    {
        int res = 0;

        try
        {
            double value = 0;
            double pow = text.Length - 1;
            int startIndex = 0;

            if (text[0] == '&')
            {
                startIndex = 2;
                pow -= 2;
            }

            for (int i = startIndex; i < text.Length; i++)
            {
                char ch = text[i];
                double num;
                if (kind == NumberKinds.Hex)
                {
                    num = ch switch
                    {
                        >= 'a' => ch - 'a' + 10,
                        >= 'A' => ch - 'A' + 10,
                        _ => ch - '0'
                    };
                }
                else
                {
                    num = ch - '0';
                }

                num *= Math.Pow((int)kind, pow);
                value += num;
                pow--;
            }

            if (value is > MaxInteger or < MinInteger)
            {
                Raise(DiagnosticDescriptors.NumberOverflow,
                    _state.GetTokenStart(),
                    _state.GetTokenEnd(),
                    text.ToString());
            }
            else
            {
                res = (int)value;
            }
        }
        catch
        {
            Raise(DiagnosticDescriptors.NumberOverflow,
                _state.GetTokenStart(),
                _state.GetTokenEnd(),
                text.ToString());
        }

        return res;
    }


    private SourceNode LexOctalNumber()
    {
        StringBuilder str = new();
        str.Append('&');
        // o|O
        str.Append(_state.GetCurrentChar());

        while (!Eof())
        {
            char c = _state.Next();

            if (c is >= '0' and <= '7')
            {
                str.Append(c);
            }
            else
            {
                break;
            }
        }

        int val = 0;
        if (str.Length > 2)
        {
            val = ParseIntegerNumberValue(str, NumberKinds.Octal);
        }
        else
        {
            int pos = _state.GetPosition();
            Raise(DiagnosticDescriptors.UnexpectedToken,
                pos, pos + 1, "8进制数字 (0-7)");
        }

        return FinishToken(SyntaxKinds.Integer, str.ToString(), 0).UpdateValue(val);
    }


    private SourceNode LexHexNumber()
    {
        StringBuilder str = new();
        str.Append('&');
        // h|H
        str.Append(_state.GetCurrentChar());

        while (!Eof())
        {
            char c = _state.Next();

            if (char.IsAsciiHexDigit(c))
            {
                str.Append(c);
            }
            else
            {
                break;
            }
        }

        int val = 0;

        if (str.Length > 2)
        {
            val = ParseIntegerNumberValue(str, NumberKinds.Hex);
        }
        else
        {
            int pos = _state.GetPosition();
            Raise(DiagnosticDescriptors.UnexpectedToken,
                pos, pos + 1, "16进制数字 (0-9, A-F, a-f)");
        }

        return FinishToken(SyntaxKinds.Integer, str.ToString(), 0).UpdateValue(val);
    }


    private SourceNode LexDecimalNumber()
    {
        StringBuilder str = new();
        bool hasDot = false;

        while (!Eof())
        {
            char c = _state.GetCurrentChar();

            if (char.IsDigit(c))
            {
                str.Append(c);
            }
            // 遇到'.'并且后一字符是数字时，视为小数。
            else if (c == '.' && _state.LookAheadIsOneOf(NumberChars))
            {
                if (hasDot)
                {
                    int pos = _state.GetPosition();
                    Raise(DiagnosticDescriptors.SyntaxError,
                        pos, pos + 1, ".");
                    break;
                }

                str.Append(c);
                hasDot = true;
            }
            else
            {
                break;
            }

            _state.Next();
        }

        string tokenText = str.ToString();

        if (hasDot)
        {
            if (!double.TryParse(tokenText, out double dbVal))
            {
                dbVal = 0;
                Raise(DiagnosticDescriptors.NumberOverflow,
                    _state.GetPosition(),
                    _state.GetTokenEnd(),
                    tokenText);
            }

            return FinishToken(SyntaxKinds.Double, tokenText, 0).UpdateValue(dbVal);
        }

        int val = ParseIntegerNumberValue(str, NumberKinds.Decimal);
        return FinishToken(SyntaxKinds.Integer, tokenText, 0).UpdateValue(val);
    }


    private enum NumberKinds
    {
        Decimal = 10,
        Hex = 16,
        Octal = 8
    }
}